spring boot 源码解析28-Log4J2LoggingSystem

前言

spring boot 中关于Log的实现我们已经分析了JavaLoggingSystem,本文就来看看Log4J2LoggingSystem,在分析之前,我们需要先分析一下Slf4JLoggingSystem–> Log4J2LoggingSystem,LogbackLoggingSystem 的父类.

解析

Slf4JLoggingSystem

  1. 字段,构造器如下:

    private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler";
    
    public Slf4JLoggingSystem(ClassLoader classLoader) {
        super(classLoader);
    }
  2. 覆写了如下方法:

    1. beforeInitialize,代码如下:

      public void beforeInitialize() {
          super.beforeInitialize();
          // 1. 配置SLF4JBridgeHandler
          configureJdkLoggingBridgeHandler();
      }

      configureJdkLoggingBridgeHandler–>配置SLF4JBridgeHandler 代码如下:

      private void configureJdkLoggingBridgeHandler() {
          try {
              // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则
              if (isBridgeHandlerAvailable()) {
                  // 1.1 删除slf4j 中root logger 配置的所有handler
                  removeJdkLoggingBridgeHandler();
                  // 1.2 为root logger添加SLF4JBridgeHandler
                  SLF4JBridgeHandler.install();
              }
          }
          catch (Throwable ex) {
              // Ignore. No java.util.logging bridge is installed.
          }
      }
      1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则

        1. 删除slf4j 中root logger 配置的所有handler,代码如下:

          private void removeJdkLoggingBridgeHandler() {
              try {
                  if (isBridgeHandlerAvailable()) {
                      try {
                          SLF4JBridgeHandler.removeHandlersForRootLogger();
                      }
                      catch (NoSuchMethodError ex) {
                          // Method missing in older versions of SLF4J like in JBoss AS 7.1
                          SLF4JBridgeHandler.uninstall();
                      }
                  }
              }
              catch (Throwable ex) {
                  // Ignore and continue
              }
          }
        2. 为root logger添加SLF4JBridgeHandler,代码如下:

          public static void install() {
              LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());
          }
    2. cleanUp–>删除slf4j 中root logger 配置的所有handler,代码如下:

      public void cleanUp() {
          removeJdkLoggingBridgeHandler();
      }
    3. loadConfiguration–>设置系统属性,代码如下:

      protected void loadConfiguration(LoggingInitializationContext initializationContext,
              String location, LogFile logFile) {
          Assert.notNull(location, "Location must not be null");
          if (initializationContext != null) {
              applySystemProperties(initializationContext.getEnvironment(), logFile);
          }
      }

      调用父类(AbstractLoggingSystem)中的 applySystemProperties方法,代码如下:

      protected final void applySystemProperties(Environment environment, LogFile logFile) {
          new LoggingSystemProperties(environment).apply(logFile);
      }

Log4J2LoggingSystem

  1. 字段如下:

    private static final String FILE_PROTOCOL = "file";
    
    private static final LogLevels<Level> LEVELS = new LogLevels<Level>();
    
    static {
        LEVELS.map(LogLevel.TRACE, Level.TRACE);
        LEVELS.map(LogLevel.DEBUG, Level.DEBUG);
        LEVELS.map(LogLevel.INFO, Level.INFO);
        LEVELS.map(LogLevel.WARN, Level.WARN);
        LEVELS.map(LogLevel.ERROR, Level.ERROR);
        LEVELS.map(LogLevel.FATAL, Level.FATAL);
        LEVELS.map(LogLevel.OFF, Level.OFF);
    }
    
    //  在beforeInitialize中添加该filter,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了
    private static final Filter FILTER = new AbstractFilter() {
    
        @Override
        public Result filter(LogEvent event) {
            return Result.DENY;
        }
    
        @Override
        public Result filter(Logger logger, Level level, Marker marker, Message msg,
                Throwable t) {
            return Result.DENY;
        }
    
        @Override
        public Result filter(Logger logger, Level level, Marker marker, Object msg,
                Throwable t) {
            return Result.DENY;
        }
    
        @Override
        public Result filter(Logger logger, Level level, Marker marker, String msg,
                Object... params) {
            return Result.DENY;
        }
    
    };
    
    public Log4J2LoggingSystem(ClassLoader classLoader) {
        super(classLoader);
    }
    
  2. 方法如下:

    1. getStandardConfigLocations –> 获取配置文件,代码如下:

      protected String[] getStandardConfigLocations() {
          return getCurrentlySupportedConfigLocations();
      }

      调用:

      private String[] getCurrentlySupportedConfigLocations() {
          List<String> supportedConfigLocations = new ArrayList<String>();
          // 1. 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持
          if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) {
              Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml");
          }
          // 2. 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn
          if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
              Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn");
          }
          // 3. 默认加入log4j2.xml
          supportedConfigLocations.add("log4j2.xml");
          return supportedConfigLocations
                  .toArray(new String[supportedConfigLocations.size()]);
      }
      1. 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持
      2. 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn
      3. 默认加入log4j2.xml

      因此,默认情况下返回的是log4j2.json, log4j2.jsn, log4j2.xml

    2. beforeInitialize,代码如下:

      public void beforeInitialize() {
          LoggerContext loggerContext = getLoggerContext();
          if (isAlreadyInitialized(loggerContext)) {
              return;
          }
          super.beforeInitialize();
          loggerContext.getConfiguration().addFilter(FILTER);
      }
      1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return
      2. 调用父类的beforeInitialize,配置SLF4JBridgeHandler
      3. 添加FILTER,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了
    3. initialize,代码如下:

      public void initialize(LoggingInitializationContext initializationContext,
              String configLocation, LogFile logFile) {
          // 1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,
          // 则意味着已经初始化过了,此时直接return
          LoggerContext loggerContext = getLoggerContext();
          if (isAlreadyInitialized(loggerContext)) {
              return;
          }
          // 2. 删除FILTER,此时意味着已经初始化成功了
          loggerContext.getConfiguration().removeFilter(FILTER);
          // 3. 加载配置文件
          super.initialize(initializationContext, configLocation, logFile);
          // 4. 向LoggerContext中的ExternalContext 存放-->org.springframework.boot.logging.LoggingSystem,标记成功初始化
          markAsInitialized(loggerContext);
      }
      1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return
      2. 删除FILTER,此时意味着已经初始化成功了
      3. 加载配置文件
      4. 向LoggerContext中的ExternalContext 存放–>org.springframework.boot.logging.LoggingSystem,标记成功初始化.代码如下:

        private void markAsInitialized(LoggerContext loggerContext) {
            loggerContext.setExternalContext(LoggingSystem.class.getName());
        }
    4. loadDefaults–>加载默认的配置文件:

      protected void loadDefaults(LoggingInitializationContext initializationContext,
              LogFile logFile) {
          // 1. 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的
          if (logFile != null) {
              loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile);
          }
          // 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml
          else {
              loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile);
          }
      }
      1. 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的
      2. 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml

      loadConfiguration实现如下:

      protected void loadConfiguration(String location, LogFile logFile) {
          Assert.notNull(location, "Location must not be null");
          try {
              // 1. 获得LoggerContext
              LoggerContext ctx = getLoggerContext();
              // 2.将location转换为URL
              URL url = ResourceUtils.getURL(location);
              // 3.根据url 获得对应的ConfigurationSource
              ConfigurationSource source = getConfigurationSource(url);
              // 4. 启动
              ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
          }
          catch (Exception ex) {
              throw new IllegalStateException(
                      "Could not initialize Log4J2 logging from " + location, ex);
          }
      }
      1. 获得LoggerContext
      2. 将location转换为URL
      3. 根据url 获得对应的ConfigurationSource.代码如下:

        private ConfigurationSource getConfigurationSource(URL url) throws IOException {
            InputStream stream = url.openStream();
            if (FILE_PROTOCOL.equals(url.getProtocol())) {// 如果是file协议
                return new ConfigurationSource(stream, ResourceUtils.getFile(url));
            }
            // 2. 其他,由于当前是classpath 协议,因此会执行到这里
            return new ConfigurationSource(stream, url);
        }
      4. 启动
    5. reinitialize –>调用时机:在log4j初始化时,在类路径下加载的以下任一1个配置文件:log4j2.json, log4j2.jsn, log4j2.xml,通过LogFile等于null,则调用该方法.代码如下:

      protected void reinitialize(LoggingInitializationContext initializationContext) {
          getLoggerContext().reconfigure();
      }

      log4j 不会直接删除所有的Loggers在重新配置的阶段,而是会重新创建LoggerConfig,然后进行替换.旧的LoggerConfig,Appenders,Filters 会被释放

    6. setLogLevel.代码如下:

      public void setLogLevel(String loggerName, LogLevel logLevel) {
          // 1. 转换LogLevel 为log4j2的LogLevel
          Level level = LEVELS.convertSystemToNative(logLevel);
          // 2.根据loggerName 获得对应的LoggerConfig
          LoggerConfig loggerConfig = getLoggerConfig(loggerName);
          // 3. 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改
          if (loggerConfig == null) {
              loggerConfig = new LoggerConfig(loggerName, level, true);
              getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig);
          }
          else {
              loggerConfig.setLevel(level);
          }
          // 4. 更新
          getLoggerContext().updateLoggers();
      }
      1. 转换LogLevel 为log4j2的LogLevel
      2. 根据loggerName 获得对应的LoggerConfig
      3. 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改
      4. 更新
    7. getLoggerConfigurations –> 获得所有的配置文件.代码如下:

      public List<LoggerConfiguration> getLoggerConfigurations() {
          List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>();
          Configuration configuration = getLoggerContext().getConfiguration();
          for (LoggerConfig loggerConfig : configuration.getLoggers().values()) {
              result.add(convertLoggerConfiguration(loggerConfig));
          }
          Collections.sort(result, CONFIGURATION_COMPARATOR);
          return result;
      }
      1. 获得Configuration中配置的Logger,遍历之
      2. 将LoggerConfig 转换为LoggerConfiguration.代码如下:

        private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) {
            if (loggerConfig == null) {
                return null;
            }
            LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel());
            String name = loggerConfig.getName();
            if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) {
                name = ROOT_LOGGER_NAME;
            }
            return new LoggerConfiguration(name, level, level);
        }
        1. 如果LoggerConfig等于null,则返回null
        2. 将log4j配置的日志级别转换为LogLevel
        3. 如果logger的名字等于root,或者不存在,则将其赋值为root
        4. 实例化LoggerConfiguration
      3. 排序–>将root logger 排在第1位,其他的按照字典顺序排序
    8. getShutdownHandler–>在ShutdownHandler中直接调用LoggerContext的stop方法.代码如下:

      public Runnable getShutdownHandler() {
          return new ShutdownHandler();
      }

      ShutdownHandler 代码如下:

      private final class ShutdownHandler implements Runnable {
      
          @Override
          public void run() {
              getLoggerContext().stop();
          }
      }
    9. cleanUp–>调用时机,当spring boot 发出ContextClosedEvent事件时调用,代码如下:

      public void cleanUp() {
          super.cleanUp();
          LoggerContext loggerContext = getLoggerContext();
          markAsUninitialized(loggerContext);
          loggerContext.getConfiguration().removeFilter(FILTER);
      }
      1. 调用父类的cleanUp方法,删除rootLogger配置的handler
      2. 获得LoggerContext
      3. 设置ExternalContext 为null,代码如下:

        private void markAsUninitialized(LoggerContext loggerContext) {
            loggerContext.setExternalContext(null);
        }
      4. 删除FILTER

Log4J2LoggingSystem 集成

  1. 由于spring boot 默认依赖的logback,因此我们需要去除,修改pom文件如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  2. 加入 Log4J2LoggingSystem的依赖.pom文件如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

Log4J2LoggingSystem生命周期

  1. 由于SpringBootConfigurationFactory继承了ConfigurationFactory,因此当使用Log4J2LoggingSystem 的时候,由于log还没有初始化,此时先使用DefaultConfiguration,后续会使用指定的配置.代码如下:

    @Plugin(name = "SpringBootConfigurationFactory", category = ConfigurationFactory.CATEGORY)
    @Order(0)
    public class SpringBootConfigurationFactory extends ConfigurationFactory {
    
    private static final String[] TYPES = { ".springboot" };
    
    @Override
    protected String[] getSupportedTypes() {
        return TYPES;
    }
    
    @Override
    public Configuration getConfiguration(LoggerContext loggerContext,
            ConfigurationSource source) {
        if (source != null && source != ConfigurationSource.NULL_SOURCE) {
            if (LoggingSystem.get(loggerContext.getClass().getClassLoader()) != null) {
                return new DefaultConfiguration();
            }
        }
        return null;
    }
    }

    当在类路径下存在.springboot结尾的文件,则会调用该类的getConfiguration方法.由于在spring-boot/src/main/resources/ 下,存在log4j2.springboot,因此该类会被执行.其文件内容如下:

    See SpringBootConfigurationFactory

    此时由于传入的ConfigurationSource不等于null,此时传入的是log4j2.springboot,因此会返回DefaultConfiguration

  2. ApplicationStartingEvent事件处理–>执行Log4J2LoggingSystem#beforeInitialize方法.在该方法中会调用Slf4JLoggingSystem#configureJdkLoggingBridgeHandler,会判断org.slf4j.bridge.SLF4JBridgeHandler是否存在,此时由于加入了spring-boot-starter-log4j2,因此加入了jul-to-slf4 jar 包, 该类就是在该包中,因此会执行后续操作,代码如下:

    private void configureJdkLoggingBridgeHandler() {
        try {
            // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则
            if (isBridgeHandlerAvailable()) {
                // 1.1 删除slf4j 中root logger 配置的所有handler
                removeJdkLoggingBridgeHandler();
                // 1.2 为root logger添加SLF4JBridgeHandler
                SLF4JBridgeHandler.install();
            }
        }
        catch (Throwable ex) {
            // Ignore. No java.util.logging bridge is installed.
        }
    }
  3. ApplicationEnvironmentPreparedEvent–> 最终会调用Log4J2LoggingSystem#initialize.

  4. ApplicationPreparedEvent事件–> 向beanFactory进行注册.

  5. ContextClosedEvent–>执行Log4J2LoggingSystem#cleanUp方法.
  6. jvm退出前,执行Log4J2LoggingSystem注册的ShutdownHandler,将LoggerContext停止掉.代码如下:

    public void run() {
        getLoggerContext().stop();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值